package com.totsp.crossword.io;

import com.totsp.crossword.puz.Box;
import com.totsp.crossword.puz.Puzzle;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

import java.io.DataOutputStream;
import java.io.InputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

/**
 * Created by Doug on 1/19/14.
 */
public class XPFIO {

    protected static final Logger LOG = Logger.getLogger("com.totsp.crossword");

    public static boolean convertXPFPuzzle(InputStream is, DataOutputStream os,
                                           Date d) {
        try {
            Puzzle puz = readPuzzle(is);
            puz.setDate(d);
            puz.setVersion(IO.VERSION_STRING);

            IO.saveNative(puz, os);

            return true;
        } catch (Exception e) {
            e.printStackTrace();
            System.err.println("Unable to parse XML file: " + e.getMessage());

            return false;
        }

    }

    public static Puzzle readPuzzle(InputStream is) {
        Puzzle puz = new Puzzle();
        SAXParserFactory factory = SAXParserFactory.newInstance();

        try {
            SAXParser parser = factory.newSAXParser();

            XMLReader xr = parser.getXMLReader();
            xr.setContentHandler(new XPFXMLParser(puz));
            xr.parse(new InputSource(is));

            puz.setVersion(IO.VERSION_STRING);

        } catch (Exception e) {
            e.printStackTrace();
            System.err.println("Unable to parse XML file: " + e.getMessage());
            throw new RuntimeException(e);
        }
        return puz;
    }

    private static class XPFXMLParser extends DefaultHandler {
        private Puzzle m_Puz;
        private enum STATE {
            NULL,
            TITLE,
            AUTHOR,
            DATE,
            ROWS,
            COLS,
            GRID,
            CLUES,
            NOTES,
            COPYRIGHT
        }
        private enum DIR { ACROSS, DOWN }
        private int curRow = 0;
        private int curCol = 0;
        private int curClueNum = 0;
        private int maxClueNum = 0;
        private DIR curDir;
        private Box boxes[][];
        private int numClues = 0;
        private Map<Integer, String> acrossNumToClueMap = new HashMap<Integer, String>();
        private Map<Integer, String> downNumToClueMap = new HashMap<Integer, String>();
        private StringBuilder strBuffer;

        private STATE m_State = STATE.NULL;

        public XPFXMLParser(Puzzle puz) {
            m_Puz = puz;
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
            localName = localName.trim();

            String name = (localName.length() == 0) ? qName.trim() : localName ;

            //LOG.info("Begin element: " + name);

            if (name.equalsIgnoreCase("Title")) {
                m_State = STATE.TITLE;
                strBuffer = new StringBuilder();
            }
            else if (name.equalsIgnoreCase("Author")) {
                m_State = STATE.AUTHOR;
                strBuffer = new StringBuilder();
            }
            else if (name.equalsIgnoreCase("Date")) {
                m_State = STATE.DATE;
                strBuffer = new StringBuilder();
            }
            else if (name.equalsIgnoreCase("Rows")) {
                m_State = STATE.ROWS;
                strBuffer = new StringBuilder();
            }
            else if (name.equalsIgnoreCase("Cols")) {
                m_State = STATE.COLS;
                strBuffer = new StringBuilder();
            }
            else if (name.equalsIgnoreCase("Row")) {
                m_State = STATE.GRID;
                strBuffer = new StringBuilder();
            }
            else if (name.equalsIgnoreCase("Clue")) {
                m_State = STATE.CLUES;
                curRow = Integer.parseInt(atts.getValue("Row"));
                curCol = Integer.parseInt(atts.getValue("Col"));
                curClueNum = Integer.parseInt(atts.getValue("Num"));
                curDir = atts.getValue("Dir").equalsIgnoreCase("Across") ? DIR.ACROSS : DIR.DOWN;
                if(curClueNum > maxClueNum)
                    maxClueNum = curClueNum;
                numClues++;
                strBuffer = new StringBuilder();
            }
            else if (name.equalsIgnoreCase("ConNotes")) {
                m_State = STATE.NOTES;
                strBuffer = new StringBuilder();
            }
            else if (name.equalsIgnoreCase("Publisher")) {
                m_State = STATE.COPYRIGHT;
                strBuffer = new StringBuilder();
            }
            else
                m_State = STATE.NULL;


        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException {

            String data = new String(ch, start, length);
            //LOG.info("Characters: " + data);

            if (strBuffer != null) {
                strBuffer.append(ch, start, length);
            }
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException {
            //LOG.info("Ending element: " + localName);

            switch(m_State) {
                case TITLE:
                    m_Puz.setTitle(strBuffer.toString());
                    m_State = STATE.NULL;
                    strBuffer = null;
                    break;
                case AUTHOR:
                    m_Puz.setAuthor(strBuffer.toString());
                    m_State = STATE.NULL;
                    strBuffer = null;
                    break;
                case ROWS:
                    m_Puz.setHeight(Integer.parseInt(strBuffer.toString()));
                    if(m_Puz.getWidth() != 0) {
                        boxes = new Box[m_Puz.getHeight()][m_Puz.getWidth()];
                    }
                    m_State = STATE.NULL;
                    strBuffer = null;
                    break;
                case COLS:
                    m_Puz.setWidth(Integer.parseInt(strBuffer.toString()));
                    if(m_Puz.getHeight() != 0) {
                        boxes = new Box[m_Puz.getHeight()][m_Puz.getWidth()];
                    }
                    m_State = STATE.NULL;
                    strBuffer = null;
                    break;
                case GRID:
                    String line = strBuffer.toString();
                    for(curCol = 0; curCol < m_Puz.getWidth(); curCol++) {
                        char cBox = line.charAt(curCol);

                        if (cBox != '.') {
                            Box b = new Box();
                            b.setSolution(cBox);
                            boxes[curRow][curCol] = b;
                        }
                    }
                    curRow++;
                    m_State = STATE.NULL;
                    strBuffer = null;
                    break;
                case CLUES:
                    if(curDir == DIR.ACROSS)
                        acrossNumToClueMap.put(curClueNum,strBuffer.toString());
                    else
                        downNumToClueMap.put(curClueNum,strBuffer.toString());
                    m_State = STATE.NULL;
                    strBuffer = null;
                    break;
                case COPYRIGHT:
                    m_Puz.setCopyright(strBuffer.toString());
                    m_State = STATE.NULL;
                    strBuffer = null;
                    break;
            }
        }





        @Override
        public void endDocument() throws SAXException {
            m_Puz.setBoxes(boxes);

            assert numClues == acrossNumToClueMap.size() + downNumToClueMap.size();

            String[] rawClues = new String[numClues];
            int i = 0;

            for (int clueNum = 1; clueNum <= maxClueNum; clueNum++) {
                if (acrossNumToClueMap.containsKey(clueNum)) {
                    rawClues[i] = acrossNumToClueMap.get(clueNum);
                    i++;
                }

                if (downNumToClueMap.containsKey(clueNum)) {
                    rawClues[i] = downNumToClueMap.get(clueNum);
                    i++;
                }
            }

            m_Puz.setRawClues(rawClues);
        }
    }

}
